<?php
/**
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 3 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

/**
 * Lisa Persistence Layer Oracle implementation
 * @package Core
 * @subpackage LPL
 * @author kovacsricsi
 */
namespace Core\LPL\Oracle;

class LPL extends \Core\LPL\ALPL {
	/**
	 * Sequence for PK.
	 * @access protected
	 * @var string
	 */
	protected $_sequence = null;
	
	/**
	 * Returns with entities whitch matced to Query.
	 * @access public
	 * @return array
	 */
	public function getAll(\Core\LPL\Query $query)
	{
		$entites = array();

		foreach($this->_select($query) as $data) {
			$entites[] = $this->_getEntity($data);
		}

		return $entites;
	}

	/**
	 * Returns with first entity whitch matched to Query and $oder pattern.
	 * @access pulic
	 * @return Entity
	 */
	public function getEntity(\Core\LPL\Query $query)
	{
		$data = $this->_select($query, false);
		if (!$data) {
			return null;
		}

		return $this->_getEntity($data);
	}

	/**
	 * Returns the number of results whitch matched to Query.
	 * @access public
	 * @return integer
	 */
	public function count(\Core\LPL\Query $query)
	{
		$sql = "SELECT COUNT(1) FROM " . $this->_tableName;

		$sql .= $query->getJoin();
		$sql .= $query->getWhere();
		$sql .= $query->getGroupBy();
		$sql .= $query->getOrderBy();
		$sql .= $query->getLimit();

		$count = $this->_connection->getOne($sql, $query->getPrepare());
		return $count["COUNT(1)"];
	}

	/**
	 * Validate Entity to commit.
	 * @access public
	 * @throws Exception
	 * @return void
	 */
	public function validate(\Core\LPL\Entity $entity)
	{
		foreach($this->_columns as $column) {
			$field = $column["COLUMN_NAME"];
			if (($field !== $this->_primaryKey) && (strtoupper($column["NULLABLE"]) == "N") && ($column["DATA_DEFAULT"] === null) && ($entity->$field === null)) {
				throw new \Core\LPL\Exception("Missed requested field : " . $field);
			}
		}
	}

	/**
	 * Commit Entity.
	 * @access public
	 * @throws Exception
	 * @return \Core\LPL\Entity
	 */
	public function commit(\Core\LPL\Entity $entity)
	{
		$this->validate($entity);

		$data = array();

		foreach($this->_columns as $column) {
			$field = $column["COLUMN_NAME"];
			if ($entity->$field) {
				$data[$field] = $entity->$field;
			}
		}

		$pk = $this->_primaryKey;
		if ($entity->$pk) {
			return $this->_update($data);
		} else {
			return $this->_insert($data);
		}
	}

	 /**
	  * Insert new Entity to database.
	  * @access protected
	  * @param array $data
	  * @return \Core\LPL\Entity
	  */
	protected function _insert(array $data)
	{
		$sql = "INSERT INTO " . $this->_tableName . " (";

		$tmp = array();
		foreach($data as $k => $v) {
			$tmp[] = $k;
		}
		$sql .= join(",", $tmp) . ") VALUES (";
		$sql .= ":" . join(", :", $tmp) . ")";

		$this->_connection->execute($sql, $data);

		if ($this->_sequence === "") {
			throw new \Core\LPL\Exception("Sequence not set! Cannot return with new Entity. Set sequence name first!");
		} elseif ($this->_sequence) {
			$sql = "SELECT " . $this->_sequence . ".CURRVAL AS CV FROM DUAL";
			$cv = $this->_connection->getOne($sql);

			$data[$this->_primaryKey] = $cv["CV"];
		}

		$sql = "SELECT * FROM " . $this->_tableName . " WHERE " . $this->_fullPrimaryKey . " = :entityPrimaryKey";
		return $this->_getEntity($this->_connection->getOne($sql, array("entityPrimaryKey" => $data[$this->_primaryKey])));
	}

	 /**
	  * Update Entity in database.
	  * @access protected
	  * @param array $data
	  * @return \Core\LPL\Entity
	  */
	protected function _update(array $data)
	{
		$sql = "UPDATE " . $this->_tableName . " SET ";

		$tmp = array();

		foreach($data as $k => $v) {
			$tmp[] = " " . $k . " =:" . (string)$k;
		}

		$sql .= join(",", $tmp);

		$sql .= " WHERE " . $this->_fullPrimaryKey . " = :entityPrimaryKey";

		$data["entityPrimaryKey"] = $data[$this->_primaryKey];

		$this->_connection->execute($sql, $data);

        $sql = "SELECT * FROM " . $this->_tableName . " WHERE " . $this->_fullPrimaryKey . " = :entityPrimaryKey";
		return $this->_getEntity($this->_connection->getOne($sql, $data));
	}

	/**
	 * Delete Entity.
	 * @access public
	 * @throws Exception
	 * @return void
	 */
	public function delete(\Core\LPL\Entity $entity)
	{
		$this->_connection->execute("DELETE FROM " . $this->_tableName . " WHERE " . $this->_fullPrimaryKey . " = :pk")->addPrepare("pk", $primaryKey);
	}

	/**
	 * Set AutoIncrement SEQUENCE.
	 * @access public
	 * @param string $name
	 * @return void
	 */
	public function setSequenceName($name)
	{
		$this->_sequence = $name;
	}

	/**
	 * Returns with entities data.
	 * @access protected
	 * @param \Core\LPL\Query $query
	 * @param boolean $all
	 * @return array
	 */
	protected function _select(\Core\LPL\Query $query, $all = true)
	{
		$sql = "SELECT " . $query->getSelect() . " FROM " . $this->_tableName;

		$sql .= $query->getJoin();
		$sql .= $query->getWhere();
		$sql .= $query->getGroupBy();
		$sql .= $query->getOrderBy();
		$sql .= $query->getLimit();

		return $all === true ? $this->_connection->getAll($sql, $query->getPrepare()) : $this->_connection->getOne($sql, $query->getPrepare());
	}

	/**
	 * Returns with columns of table.
	 * @access protected
	 * @return array
	 */
	protected function _getColumns()
	{
		return $this->_connection->getAll("SELECT * FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '" . $this->_tableName . "'");
	}

	/**
	 * Set primary key for entities.
	 * @access protected
	 * @throws Exception
	 * @return void
	 */
	protected function _setPrimaryKey($pk)
	{
		if ($pk !== null) {
			if ($this->_connection->getOne("SELECT cons.constraint_type FROM all_constraints cons, all_cons_columns cols WHERE cols.table_name = '" . $this->_tableName . "' AND cons.constraint_name = cols.constraint_name AND cols.column_name = '" . $pk . "' AND cons.constraint_type = 'U'")) {
				$this->_primaryKey = $pk;
			} else {
				throw new \Core\LPL\Exception("Primary key not unique!");
			}
		} else {
			$pkData = $this->_connection->getOne("SELECT cols.column_name, cons.index_name FROM all_constraints cons, all_cons_columns cols WHERE cols.table_name = '" . $this->_tableName . "' AND cons.constraint_name = cols.constraint_name AND cons.constraint_type = 'P'");
			if (isset($pkData["COLUMN_NAME"])) {
				$this->_primaryKey = $pkData["COLUMN_NAME"];

				$seqName = $this->_sequence = rtrim($pkData["INDEX_NAME"], "_PK") . "_SEQ";
				$seqCount = $this->_connection->getOne("SELECT COUNT(1) FROM all_sequences WHERE sequence_name = '" . $seqName . "'");
				if ($seqCount["COUNT(1)"] > 0) {
					$this->_sequence = $seqName;
				} else {
					$this->_sequence = "";
				}
			} else {
				throw new \Core\LPL\Exception("Primary key not found!");
			}
		}
	}

	/**
	 * Set primary key for entities.
	 * @access protected
	 * @return void
	 */
	protected function _setConnection()
	{
		$this->_connection = \Core\Database\Oracle\DatabaseConnection::getConnection($this->_connectionName);
	}
}
?>